from __future__ import with_statement
import sys
import os
import time
import select
import heapq
import socket
from threading import RLock


class IntervalCallback(object):
    __slots__ = ["func", "interval", "timestamp", "cancelled"]
    def __init__(self, interval, func):
        self.interval = interval
        self.func = func
        self.cancelled = False
        self.reset()
    def __cmp__(self, other):
        return cmp(self.timestamp, other.timestamp)
    def reset(self):
        self.timestamp = time.time() + self.interval
    def invoke(self):
        if self.cancelled:
            return False
        res = self.func()
        self.reset()
        return res

class HeapQueue(object):
    def __init__(self, seq = ()):
        self._heap = list(seq)
        heapq.heapify(self._heap)
    def __len__(self):
        return len(self._heap)
    def get_timeout(self):
        if not self:
            return None
        return self._heap[0].timestamp - time.time()
    def add(self, elem):
        heapq.heappush(self._heap, elem)
    def invoke(self):
        to_add = []
        while self._heap:
            cb = self._heap[0]
            if cb.timestamp > time.time():
                break
            heapq.heappop(self._heap)
            if cb.invoke():
                to_add.append(cb)
        for cb in to_add:
            self.add(cb)

class IoCallback(object):
    __slots__ = ["file", "func", "cancelled"]
    def __init__(self, file, func):
        self.file = file
        self.func = func
        self.cancelled = False
    def fileno(self):
        return self.file.fileno()
    def invoke(self):
        if self.cancelled:
            return False
        return self.func()

class PosixSignal(object):
    __slots__ = ["func", "rfd", "wfd", "signalled", "lock"]
    def __init__(self, func):
        self.rfd, self.wfd = os.pipe()
        self.signalled = False
        self.func = func
        self.lock = RLock()
    def __del__(self):
        self.close()
    def close(self):
        if self.rfd is None:
            return
        os.close(self.rfd)
        os.close(self.wfd)
        self.rfd = None
        self.wfd = None
    def fileno(self):
        return self.rfd
    def signal(self):
        with self.lock:
            if self.signalled:
                return
            os.write(self.wfd, "x")
            self.signalled = True
    def invoke(self):
        with self.lock:
            os.read(self.rfd, 100)
            self.signalled = False
        res = self.func()
        if not res:
            self.close()
        return res

class Win32Signal(object):
    __slots__ = ["func", "signalled", "client", "server", "lock"]
    def __init__(self, func):
        listener = socket.socket()
        listener.bind(("localhost", 0))
        listener.listen(1)
        self.client = socket.socket()
        self.client.connect(listener.getsockname())
        self.server = listener.accept()[0]
        self.lock = RLock()
        self.signalled = False
        self.func = func
    def __del__(self):
        self.close()
    def close(self):
        self.client.close()
        self.server.close()
    def fileno(self):
        return self.server.fileno()
    def signal(self):
        with self.lock:
            if self.signalled:
                return
            self.client.send("x")
            self.signalled = True
    def invoke(self):
        with self.lock:
            self.server.recv(100)
            self.signalled = False
        res = self.func()
        if not res:
            self.close()
        return res

if sys.platform == "win32":
    Signal = Win32Signal
else:
    Signal = PosixSignal

interval_callbacks = HeapQueue()
rfds = set()
wfds = set()
xfds = set()
_active = False

#===============================================================================
# registration API
#===============================================================================
def register_read(fd, callback):
    cb = IoCallback(fd, callback)
    rfds.add(cb)
    return cb

def register_signal(callback):
    sig = Signal(callback)
    rfds.add(sig)
    return sig

def register_channel(channel):
    return register_read(channel, channel.on_read)

def register_write(fd, callback):
    cb = IoCallback(fd, callback)
    wfds.add(cb)
    return cb

def register_listener(factory, port, host = "0.0.0.0", backlog = 8):
    from .channel import ListenerChannel
    chan = ListenerChannel.create(factory, port, host, backlog)
    return register_channel(chan)

def register_erroneous(fd, callback):
    cb = IoCallback(fd, callback)
    xfds.add(cb)
    return cb

def register_interval(interval, callback):
    cb = IntervalCallback(interval, callback)
    interval_callbacks.add(cb)
    return cb

if sys.platform == "win32":
    register_interval(1, lambda: True) # Ctrl+C sampler

def register(callback):
    register_interval(0, callback)

#===============================================================================
# reactor API
#===============================================================================
def start():
    global _active
    if _active:
        raise ValueError("reactor already active")
    _active = True
    try:
        work()
    finally:
        _active = False

def work():
    global _active
    while _active:
        timeout = interval_callbacks.get_timeout()
        rl, wl, xl = select.select(rfds, wfds, xfds, timeout)
        interval_callbacks.invoke()
        for all, ready in ((rfds, rl), (wfds, wl), (xfds, xl)):
            for cb in ready:
                if not cb.invoke():
                    all.remove(cb)

def stop():
    global _active
    if not _active:
        raise ValueError("reactor not active")
    _active = False

